﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Threading;
using System.Net.Sockets;
using System.IO;

namespace BuildHelper.Kontakt
{
    public class Server
    {
       
        private static object ClientSynch = new object();

        public Server()
        {
            Port = KontaktTools.Port;
            Address = KontaktTools.HostIp;
        }
        public Client OwnerClinet
        {
            get;
            set;
        }

        private TcpListener myList;

        private static Server _Instance;
        public static Server Instance
        {
            get
            {
                if (_Instance == null && KontaktTools.IsServer)
                    _Instance = new Server();
                return _Instance;
            }
        }

        public string Address
        {
            get;
            set;
        }
        public int Port
        {
            get;
            set;
        }

        public void Run()
        {
            if (myList == null)
            {
                Thread t = new Thread(new ThreadStart(ListenForClients));
                t.Start();
                try
                {
                    OwnerClinet = new Client();
                    OwnerClinet.Login = KontaktTools.ServerName;
                    OwnerClinet.ID = KontaktTools.ServerName;
                    OwnerClinet.Port = this.Port;
                    OwnerClinet.Address = this.Address;
                    OwnerClinet.Run();
                } catch {}
            }
        }
        
        public void Stop()
        {
            if (KontaktUsersFormNew.Instance != null)
            {
                try { KontaktUsersFormNew.Instance.Close(); }
                catch { }
            }
            foreach (var k in clients)
            {
                try
                {
                    k.Value.Client.Client.Disconnect(false);
                    k.Value.Client.Client.Close();
                }
                catch { }
                try
                {
                    k.Value.Client.Close();
                }
                catch { }
            }
            try
            {
                myList.Stop();
            }
            catch { }
            if (OwnerClinet != null)
            {
                try
                {
                    OwnerClinet.Stop();
                }
                catch { }
            }
            if (KontaktUsersFormNew.Instance != null)
            {
                try
                {
                    KontaktUsersFormNew.Instance.Close();
                }
                catch { }
            }
            myList = null;
            clientsList.Clear();
            clients.Clear();
        }

        private Dictionary<string, ClientInformation> clients = new Dictionary<string, ClientInformation>();
        public ClientInformation GetClient(string client)
        {
            if (client.IsNotNull() && clients.ContainsKey(client))
                return clients[client];
            return null;
        }
        public List<ClientInformation> clientsList = new List<ClientInformation>();

        private void ListenForClients()
        {
            try
            {
                IPAddress ipAd = IPAddress.Parse(Address);

                myList = new TcpListener(ipAd, Port);
                myList.Start();
               
                while (true)
                {
                    if (myList == null)
                        break;
                    TcpClient client = myList.AcceptTcpClient();
                    Thread t = new Thread(new ParameterizedThreadStart(HandleClient));
                    t.Start(client);
                }
            }
            catch { }
        }

        private byte[] GetMessageBytes(byte[] fullBytes, string msg)
        {
            List<byte> list = new List<byte>(fullBytes);
            return GetMessageBytes(list, msg);
        }
        private byte[] GetMessageBytes(List<byte> list, string msg)
        {
            List<byte> tmp = new List<byte>(list);
            byte[] last = KontaktTools.GetBytes(DefaultMessages.MSG_END);
            for (int i = 0; i < last.Length; i++)
            {
                if (tmp.Count > 0)
                    tmp.RemoveAt(tmp.Count - 1);
            }
            string prefix = msg.GetFirstMatch("((\f\\d*\f)" +
                            DefaultMessages.MSG_RECEIVER_ID + "(.*?)" + DefaultMessages.MSG_RECEIVER_ID + ")");
            byte[] first = KontaktTools.GetBytes(prefix);
            for (int i = 0; i < first.Length; i++)
            {
                if (tmp.Count > 0)
                    tmp.RemoveAt(0);
            }
            return tmp.ToArray();
        }
        private void HandleClient(object cl)
        {
            TcpClient client = cl as TcpClient;
            
            NetworkStream clientStream = client.GetStream();
            List<byte> fullMsgBytes = new List<byte>();

            byte[] message = new byte[4096];
            int bytesRead;
            string login = "";
            string msg = "";
            string ClientId = "";
            string fullMessage = "";
            string receiver = "";
            ClientInformation clInfo =null;
            System.Text.RegularExpressions.RegexOptions op = System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Singleline;
            string messageMask = "((\f\\d*\f)" +
                            DefaultMessages.MSG_RECEIVER_ID +"(.*?)" + DefaultMessages.MSG_RECEIVER_ID +
                            "(.*?)" +
                            DefaultMessages.MSG_END + ")";
           
            Dictionary<int, string> maches;

            while (true)
            {
                msg = "";
                bytesRead = 0;
                try
                {
                    //blocks until a client sends a message
                    bytesRead = clientStream.Read(message, 0, 4096);
                    if (bytesRead == 4096)
                        fullMsgBytes.AddRange(message);
                    else
                        fullMsgBytes.AddRange(message.Take(bytesRead));


                    if (bytesRead == 0)
                    {
                        //the client has disconnected from the server
                        break;
                    }
                    msg = KontaktTools.GetString(message, bytesRead);

                    if (msg.StartsWith(DefaultMessages.LOGIN))
                    {
                        login = msg;
                        int idx = msg.IndexOf("\f", 3);
                        if (idx > -1)
                        {
                            login = msg.Substring(0,  idx);
                            msg = msg.Substring(idx);
                        }

                        login = login.Replace(DefaultMessages.LOGIN, "");
                        string id = login;
                        lock (ClientSynch)
                        {
                            if (clients.ContainsKey(id))
                            {
                                try
                                {
                                    SendTo(DefaultMessages.ID, id, id);
                                }
                                catch
                                {
                                    if (clientsList.Contains(clients[id]))
                                        clientsList.Remove(clients[id]);
                                    clients.Remove(id);
                                }
                            }
                            if (!clients.ContainsKey(id))
                            {
                                clInfo = new ClientInformation(login, id, client);
                                clients.Add(id, clInfo);
                                if (id != KontaktTools.ServerName || Program.IsDebug)
                                    clientsList.Add(clInfo);
                            }
                            ClientId = id;
                        }
                        fullMsgBytes.Clear();
                    }
                    else
                    {
                        fullMessage += msg;

                        if (fullMessage.XMatch(messageMask, op))
                        {
                            #region normal message
                            maches = fullMessage.GetFirstMatch(messageMask, new int[] { 1, 2, 3, 4 }, op);
                            string msgId = maches[2];
                            receiver = maches[3];
                            msg = maches[4];

                            switch (msgId)
                            {
                                case DefaultMessages.MSG_START:
                                    if (receiver == KontaktTools.ServerName)
                                        OwnerClinet.ReceivedMessage(ClientId, login, msg);
                                    else if (clients.ContainsKey(receiver))
                                        Send(receiver, ClientId, login, msg);
                                    break;
                                case DefaultMessages.MSG_START_QUERY:
                                    if (receiver == KontaktTools.ServerName)
                                        OwnerClinet.ReceivedMessageQuery(ClientId, login, msg);
                                    else if (clients.ContainsKey(receiver))
                                        SendQuery(clients[receiver].Client.GetStream(), ClientId, login, msg);
                                    break;
                                case DefaultMessages.MSG_START_QUERY_RESULT:
                                    if (receiver == KontaktTools.ServerName)
                                        Admin.FormQuery.AddPage(ClientId, msg);
                                    break;
                                case DefaultMessages.MSG_SNAPSHOT:
                                    if (receiver == KontaktTools.ServerName)
                                        Admin.AdminTools.ShowSnapshot(ClientId, GetMessageBytes(fullMsgBytes, fullMessage));
                                    break;
                                case DefaultMessages.MSG_GET_APP_VER:
                                    FormMain.Instance.ShowDxInfoDialog(login + " ma wersje " + msg);
                                    break;
                                case DefaultMessages.MSG_BEEP:
                                    break;
                                case DefaultMessages.UNKNOWN_MESSAGE:
                                    FormMain.ShowErrorAlertDontHide("Klient " + login + " nie rozpoznaje " + msg);
                                    break;
                            }
                            fullMessage = string.Empty;
                            fullMsgBytes.Clear();
                            #endregion
                        }
                    }
                }
                catch
                {
                    //a socket error has occured
                    break;
                }
            }
            string delCl = null;
            foreach (var v in clients)
            {
                if (v.Value.Client == client)
                {
                    int idx = clientsList.IndexOf(v.Value);
                    if (idx > -1 && idx < clientsList.Count)
                        clientsList.RemoveAt(idx);
                    delCl = v.Key;
                    break;
                }
            }
            if (delCl != null)
                clients.Remove(delCl);

            if (ClientId.IsNotNull())
            {
                if (clients.ContainsKey(ClientId))
                {
                    int idx = clientsList.IndexOf(clInfo);
                    if (idx > -1 && idx < clientsList.Count)
                        clientsList.RemoveAt(idx);
                    clients.Remove(ClientId);
                }
            }
            if (client != null)
            {
                try
                {
                    client.Close();
                }
                catch { }
            }
        }
        private void Send(string receiver, string senderId, string senderNick, string msg)
        {
            SendCustom(DefaultMessages.MSG_START, clients[receiver].Client.GetStream(), senderId, senderNick, msg);
        }
        private void Send(Stream s, string senderId, string senderNick, string msg)
        {
            SendCustom(DefaultMessages.MSG_START, s, senderId, senderNick, msg);
        }
        public void SendQuery(Stream s, string senderId, string senderNick, string msg)
        {
            SendCustom(DefaultMessages.MSG_START_QUERY, s, senderId, senderNick, msg);
        }
        private void SendCustom(String prefix, Stream s, string senderId, string senderNick, string msg)
        {
            string m = prefix +
                DefaultMessages.MSG_SENDER_ID + senderId + DefaultMessages.MSG_SENDER_ID +
                DefaultMessages.MSG_SENDER_NICK + senderNick + DefaultMessages.MSG_SENDER_NICK +
                msg +
                DefaultMessages.MSG_END;

            byte[] bytes = KontaktTools.GetBytes(m);
            s.Write(bytes, 0, bytes.Length);
        }

        public void SendTo(string prefix, string reciver, string msg = "")
        {
            if (clients.ContainsKey(reciver))
            {
                SendCustom(prefix, clients[reciver].Client.GetStream(), KontaktTools.ServerName, KontaktTools.ServerName, msg);
            }
        }
        public void SendToAll(string prefix, string msg)
        {
            foreach (KeyValuePair<string, ClientInformation> kvp in clients)
            {
                if (kvp.Key != KontaktTools.ServerName)
                {
                    SendCustom(prefix, kvp.Value.Client.GetStream(), KontaktTools.ServerName, KontaktTools.ServerName, msg);
                }
            }
        }
    }

    public class ClientInformation
    {
        public ClientInformation(string login, string id, TcpClient client)
        {
            ID = id;
            UserName = login;
            Client = client;
        }
        public string UserName
        {
            get;
            set;
        }
        public string ID
        {
            get;
            set;
        }
        public TcpClient Client
        {
            get;
            set;
        }
    }
   
}
